﻿using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System;
using System.Collections.Generic;
using System.Windows.Resources;
using SilverlightContrib.Controls;

namespace GuitarChordFinder
{
    public partial class Page : UserControl
    {
        #region Fields

        private NoteData _noteData;
        private ChordTypeData _chordTypeData;
        private List<NoteMapping> _noteMappingList;
        private StringInfoChartData _stringInfoChartData;
        private ChordAudioData _chordAudioData;
        private ScaleInfoChart _scaleInfoChart;
        private List<Button> _fingerSpotList;
        private ChordAudio _currentChord;

        #endregion

        #region Constructor

        public Page()
        {
            // Required to initialize variables
            InitializeComponent();

            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }

        #endregion

        #region Event Handlers

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            btnPlayChord.Click += new RoutedEventHandler(btnPlayChord_Click);

            InitializeData();

            DefaultCoolMenuBehavior myDefaultCoolMenuBehavior = new SilverlightContrib.Controls.DefaultCoolMenuBehavior();
            myDefaultCoolMenuBehavior.MaxItemHeight = 80;
            myDefaultCoolMenuBehavior.MaxItemWidth = 80;
            noteMenu.Behavior = myDefaultCoolMenuBehavior;
            noteMenu.MenuItemClicked += new MenuIndexChangedHandler(noteMenu_MenuItemClicked);
            noteMenu.DataContext = _noteData.NoteList;

            chordtypeMenu.Behavior = myDefaultCoolMenuBehavior;
            chordtypeMenu.MenuItemClicked += new MenuIndexChangedHandler(chordtypeMenu_MenuItemClicked);
            chordtypeMenu.DataContext = _chordTypeData.ChordTypeList;

            //Set bindings
            SelectedChordPanel.DataContext = _currentChord;
        }

        private void chordtypeMenu_MenuItemClicked(object sender, SelectedMenuItemArgs e)
        {
            ChordType selectedChordType = e.Item.DataContext as ChordType;
            _currentChord.ChordType = selectedChordType;
            ShowChord();
        }

        private void noteMenu_MenuItemClicked(object sender, SelectedMenuItemArgs e)
        {
            Note selectedNote = e.Item.DataContext as Note;
            _currentChord.Note = selectedNote;
            ShowChord();
        }

        private void btnPlayChord_Click(object sender, RoutedEventArgs e)
        {
            PlayChord();
        }

        #endregion

        #region Initialize data

        private void InitializeData()
        {
            _noteData = new NoteData(); // Stores the basic notes (fixed data)
            _chordTypeData = new ChordTypeData(); // Stores the chord types (fixed data)
            _stringInfoChartData = new StringInfoChartData(); // Stores the strings, frets, and notes for each of them. (fixed data)
            _chordAudioData = new ChordAudioData(); // Stores the audio file information for each chord 
            _currentChord = new ChordAudio(); // Tracks the selected Chord

            _scaleInfoChart = new ScaleInfoChart(); // Scales from A-G# (starting point changes) and the octaves. (fxed data, except starting note)
            _noteMappingList = new List<NoteMapping>(); // generated list of notes and scales for the selected chord.
            _fingerSpotList = new List<Button>(); // List of buttons to display on the fretboard (generated data)
        }

        #endregion

        #region Show the Chord

        private void ShowChord()
        {
            // make sure something was selected
            if (_currentChord.Note == null) return;
            if (_currentChord.ChordType == null) return;

            _noteMappingList.Clear();

            // Gather the Chord audio file and display the selected Chord Description on screen
            SetCurrentChordAudio();

            // Fill the scale note chart starting with the Note letter
            FillScaleNoteChart();

            // region Now get the list of notes that we need
            List<string> notesToFind = GetChordNotes(_currentChord.ChordType);

            // Create Mappings - Check each string to see which fret to hold, or if open, or if closed
            FillNoteMappingList(notesToFind);

            #region Trim the notes in the chords down a bit

            // Now try to remove any that would require more than 4 fingers
            int fingerCount = EditNoteMapping_LimitFingers();

            // If a single fret has 3 or more non consecutive strings, make it 2.
            EditNoteMapping_TrimNonConsecutives();

            // If finger count > 4, check to see if all the notes are covered by strings 1 - 4. if so, remove strings 5 and 6 (unless they are open).
            EditNoteMapping_Limit4thFinger(notesToFind, fingerCount);

            #endregion

            // Remove the old markers
            RemoveOldFingerSpots();

            // Take the NoteMapping list and show them on screen
            DisplayNoteMapping();
        }

        #endregion

        #region Helpers for ShowChord

        private void SetCurrentChordAudio()
        {
            // Gather the Chord audio file and display the selected Chord Description on screen
            var query = (from ca in _chordAudioData.ChordAudioList
                         where ca.ChordType.Code == _currentChord.ChordType.Code && ca.Note.Code == _currentChord.Note.Code
                         select ca).First();

            if (query != null)
                _currentChord.AudioFile = query.AudioFile;
            else
                _currentChord.AudioFile = "";
        }

        private void FillScaleNoteChart()
        {
            // Figure out what note to start at
            int starter = _noteData.NoteList.IndexOf(_currentChord.Note);

            for (int i = 0; i < _scaleInfoChart.ScaleNoteList.Count; i++) // 12 loops
            {
                int j = (starter + i >= 12) ? (starter + i - 12) : (starter + i);
                _scaleInfoChart.ScaleNoteList[i].Note = new Note { Code = _noteData.NoteList[j].Code };
            }
        }

        private void DisplayNoteMapping()
        {
            foreach (var item in _noteMappingList)
            {
                var fingerSpot = new Button { Style = App.Current.Resources["FingerButtonStyle"] as Style, Content = item.NoteCode };
                fingerSpot.SetValue(Grid.RowProperty, item.StringNumber - 1);
                if (item.FretNumber == 0)
                {
                    fingerSpot.SetValue(Grid.ColumnProperty, item.FretNumber);
                    fingerSpot.HorizontalAlignment = HorizontalAlignment.Left;
                    fingerSpot.Margin = new Thickness(-12, 0, 0, 0);
                }
                else
                {
                    fingerSpot.SetValue(Grid.ColumnProperty, item.FretNumber - 1);
                }
                _fingerSpotList.Add(fingerSpot);
                fretBoardGrid.Children.Add(fingerSpot);

                // Use mp3's for all strings and fret positions. Play them when a button is clicked.
                var audioFile = item.AudioFile; // Need to do this, otherwise PlayMedia (below) will use the last item.AudioFile in the loop for all calls.
                fingerSpot.Click += ((sender1, e1) => PlayNote(audioFile));
            }
        }

        private void RemoveOldFingerSpots()
        {
            foreach (var button in _fingerSpotList)
                fretBoardGrid.Children.Remove(button);
            _fingerSpotList.Clear();
        }

        private void EditNoteMapping_Limit4thFinger(List<string> notesToFind, int fingerCount)
        {
            if (fingerCount == 4)
            {
                // Get a list of the notes that make up the chord
                List<string> copyOfNotesToFind = new List<string>();
                foreach (var noteCode in notesToFind)
                    copyOfNotesToFind.Add(noteCode);
                // Look through the mapping list and see if the notes are all covered in strings 1 - 4 or not
                foreach (var mapping in _noteMappingList.Where(m => m.StringNumber < 5))
                {
                    if (copyOfNotesToFind.Contains(mapping.NoteCode))
                        copyOfNotesToFind.Remove(mapping.NoteCode);
                }

                var noteMap5 = _noteMappingList.Where(m => m.StringNumber == 5).FirstOrDefault();
                var noteMap6 = _noteMappingList.Where(m => m.StringNumber == 6).FirstOrDefault();
                switch (copyOfNotesToFind.Count)
                {
                    case 0:
                        // Check if string 5 should be nixed
                        if (noteMap5 != null && noteMap5.FretNumber > 0)
                        {
                            // If 5 goes, 6 goes too
                            _noteMappingList.Remove(_noteMappingList.Where(m => m.StringNumber == 5).FirstOrDefault());
                            _noteMappingList.Remove(_noteMappingList.Where(m => m.StringNumber == 6).FirstOrDefault());
                        }
                        else
                        {
                            // Check if string 6 should be nixed
                            if (noteMap6 != null && noteMap6.FretNumber > 0)
                                _noteMappingList.Remove(_noteMappingList.Where(m => m.StringNumber == 6).FirstOrDefault());
                        }
                        break;
                    case 1:
                        if (copyOfNotesToFind.Contains(noteMap5.NoteCode))
                            if (noteMap6 != null && noteMap6.FretNumber > 0)
                                _noteMappingList.Remove(_noteMappingList.Where(m => m.StringNumber == 6).FirstOrDefault());
                        break;
                }
            }
        }

        private void EditNoteMapping_TrimNonConsecutives()
        {
            var tempQuery = (from m in _noteMappingList group m by m.FretNumber into g where g.Key != 0 select new { FretNumber = g.Key, StringCount = g.Count() });
            var fretsToCheck = from f in tempQuery where f.StringCount >= 3 select f.FretNumber;
            NoteMapping mappingToRemove = null;
            foreach (var fretNumber in fretsToCheck)
            {
                var cnt = 0;
                var previousString = -1;
                foreach (var map in _noteMappingList.Where(m => m.FretNumber == fretNumber))
                {
                    if (previousString == -1)
                    {
                        cnt++;
                        previousString = map.StringNumber;
                        continue;
                    }

                    if (map.StringNumber > (previousString + 1))
                        cnt++;
                    previousString = map.StringNumber;
                    if (cnt == 3)
                    {
                        mappingToRemove = map;
                        break;
                    }
                }
                if (mappingToRemove != null)
                    break;
            }
            EditNoteMapping_Remove6thString(mappingToRemove);
        }

        private void EditNoteMapping_Remove6thString(NoteMapping mappingToRemove)
        {
            if (mappingToRemove != null)
            {
                if (_noteMappingList.Count == 6 && mappingToRemove.StringNumber == 6)
                    _noteMappingList.Remove(_noteMappingList[5]);
                _noteMappingList.Remove(mappingToRemove);
            }
        }

        private int EditNoteMapping_LimitFingers()
        {
            var fingerCount = 0;
            var previousFret = 0;
            NoteMapping mappingToRemove = null;
            foreach (var mapping in _noteMappingList)
            {
                if (mapping.FretNumber != previousFret || (mapping.FretNumber == previousFret && mapping.FretNumber > 1))
                {
                    if (fingerCount == 4)
                    {
                        if (mapping.FretNumber > 0)
                        {
                            // we have enough already, remove the rest
                            mappingToRemove = mapping;
                            break;
                        }
                    }
                    else
                    {
                        if (mapping.FretNumber > 0) fingerCount++;
                        previousFret = mapping.FretNumber;
                    }
                }
            }
            EditNoteMapping_Remove6thString(mappingToRemove);
            return fingerCount;
        }

        private void FillNoteMappingList(List<string> notesToFind)
        {
            for (int stringNumber = 0; stringNumber < _stringInfoChartData.StringNotesList.Count; stringNumber++) // loop 0 - 5 
            {
                //Loop through the frets on the string and create the mapping
                var stringInfo = _stringInfoChartData.StringNotesList[stringNumber];
                for (int fretNumber = 0; fretNumber < stringInfo.FretNoteList.Count; fretNumber++)
                {
                    var fretNote = _stringInfoChartData.StringNotesList[stringNumber].FretNoteList[fretNumber];
                    if (notesToFind.Contains(fretNote.Note.Code)) // Check to see if the note is this one.
                    {
                        // remember this string, its fret #, and note, then get out.
                        _noteMappingList.Add(
                            new NoteMapping
                                {
                                    StringNumber = stringInfo.StringNumber,
                                    FretNumber = fretNote.FretNumber,
                                    NoteCode = fretNote.Note.Code,
                                    AudioFile = stringInfo.FretNoteList.Where(f => f.FretNumber == fretNumber).FirstOrDefault().AudioFile
                                });
                        break;
                    }
                }
            }
        }

        private List<string> GetChordNotes(ChordType selectedChordType)
        {
            List<string> notesToFind = new List<string>();
            foreach (var i in (selectedChordType).ScaleSlots)
                notesToFind.Add(_scaleInfoChart.ScaleNoteList[i - 1].Note.Code);
            return notesToFind;
        }

        #endregion

        #region Play the Audio

        private void PlayNote(string filename)
        {
            StreamResourceInfo sri = Application.GetResourceStream(new Uri("GuitarChordFinder;component/sounds/" + filename, UriKind.Relative));
            if (sri == null) return; // Get out if there is no audio file
            notePlayer.SetSource(sri.Stream);
            //PAPA - If you want to stream the audio, use the following line of code instead
            //notePlayer.Source = new Uri("http://localhost:7087/sounds/" + filename, UriKind.Absolute);
            notePlayer.Position = TimeSpan.FromSeconds(0);
            notePlayer.Play();
        }

        private void PlayChord()
        {
            if (_currentChord.AudioFile.Length == 0) return;
            chordPlayer.AutoPlay = true;
            chordPlayer.Position = TimeSpan.FromSeconds(0);
            chordPlayer.Play();
            chordPlayer.AutoPlay = false;
        }

        #endregion
    }
}
